package com.smssalama;

import java.util.Date;

import com.smssalama.storage.contact.Contact;
import com.smssalama.storage.contact.ContactFilter;
import com.smssalama.storage.framework.ObjectStore;
import com.smssalama.storage.framework.StorageEncryptor;
import com.smssalama.storage.framework.ObjectStore.ObjectEnumeration;
import com.smssalama.storage.sms.SMSMessageIn;
import com.smssalama.storage.sms.SMSMessageOut;
import com.smssalama.transport.ISMSTransportListener;
import com.smssalama.transport.SMSTransport;
import com.smssalama.transport.SendMessageCallback;

/**
 * 
 * A singleton that handles the sending of messages by forwarding to {@link SMSTransport}. 
 * Also listens for incoming messages from the provided {@link SMSTransport}.
 * 
 * If there is a user signed in, incoming messages are stored in the user's message store.
 * If there is no signed in user, incoming messages are stored in a temporary store until a user signs in. 
 *  
 * @author Arnold P. Minde
 *
 */
public final class TransportBridge implements ISMSTransportListener {
	private static TransportBridge bridge;
	static ObjectStore temporaryMessageStore;// = new ObjectStore("", // StorageEncryptor.NULL_ENCRYPTOR);

	private SMSTransport transport;

	protected TransportBridge() {
	}

	public static synchronized TransportBridge getBridge() {
		if (TransportBridge.bridge == null) {
			TransportBridge.bridge = new TransportBridge();
		}
		return TransportBridge.bridge;
	}

	public SMSTransport getTransport() {
		return this.transport;
	}

	public void sendSMS(String number, String sharedKey, String text)
			throws Exception {
		final SMSMessageOut sms = new SMSMessageOut(number, new Date()
				.getTime(), text);
		SMSSalamaSession.getSession().getUserSettings().getMessagesStore()
				.persistStorable(sms);
		
		byte[] data = sms.encrypt(sharedKey);

		this.transport.sendBinarySMS(sms.getRecordId(), number, this.transport
				.getPort(), data, new SendMessageCallback() {
			public void updateStatus(int status) {
				sms.setStatus(status);
				try {
					SMSSalamaSession.getSession().getUserSettings()
								.getMessagesStore().persistStorable(sms);
				} catch (Exception e) {
					e.printStackTrace();
					//TODO: We have failed to save the status of outgoing message. What should we do?
				}
			}

			public void onFailure(Exception caught) {
				updateStatus(SMSMessageOut.FAILED);
			}

			public void onSuccess() {
				updateStatus(SMSMessageOut.SENT);
			}
		});
	}

	public void setTransport(SMSTransport transport) {
		this.transport = transport;
	}

	public void onBinaryMessage(SMSTransport transport, String number,
			Date timestamp, byte[] data) {
		SMSMessageIn sms = new SMSMessageIn(number, timestamp.getTime(), data);
		try {
			// if a user is loged in, attempt to decrypt the sms, and (even if
			// we fail to decrypt) save it
			// if no user is logged in, as i might happen when the app is
			// responding to an incomming message and the user is not logged in,
			// save it in temporary storage
			if (SMSSalamaSession.getSession().isAuthenticated()) {
				if (SMSSalamaSession.getSession().getUserSettings() != null) {
					// find the contact
					ObjectEnumeration enumeration = SMSSalamaSession
							.getSession().getUserSettings().getContactsStore()
							.search(new ContactFilter(null, number), null);
					while (enumeration.hasNextObject()) {
						Contact contact = (Contact) enumeration.nextObject();
						if (contact.getSharedKey() != null) {
							try {
								sms.decrypt(contact.getSharedKey());
								break;
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					}
					SMSSalamaSession.getSession().getUserSettings()
							.getMessagesStore().persistStorable(sms);
					return;
				}
			}
			// No user has loged in, so store it in temporay store
			ObjectStore store = TransportBridge.getTemporaryStore();
			store.persistStorable(sms);
		} catch (Exception e) {
			e.printStackTrace();
			// TODO: We failed to handle the incomming message, what should we
			// do?
		}
	}

	/**
	 * @deprecated
	 */
	public void onMessageSendingStatus(SMSTransport transport, int messageId,
			int status) {
		try {
			SMSMessageOut sms = (SMSMessageOut) SMSSalamaSession.getSession()
					.getUserSettings().getMessagesStore()
					.getStorable(messageId);
			sms.setStatus(status);
			SMSSalamaSession.getSession().getUserSettings().getMessagesStore()
					.persistStorable(sms);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void onTextMessage(SMSTransport transport, String number,
			Date timestamp, String payloadData) {
		// TODO Handle plain text messages.
	}

	public static ObjectStore getTemporaryStore() throws Exception {
		if (TransportBridge.temporaryMessageStore == null) {
			TransportBridge.temporaryMessageStore = new ObjectStore(
					"tmp_inbox", StorageEncryptor.NULL_ENCRYPTOR);
		}
		return TransportBridge.temporaryMessageStore;
	}
}
